package bsh;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.meta.Scope;
import bsh.meta.ScopeType;
import bsh.meta.TypeWrapper;

/**
 * @author takeshi
 */
public class IfNodeCompiler implements NodeCompiler {

    private final BSHIfStatement node;

    private final CompilationUnit unit;

    /**
     * @param node node
     * @param unit unit
     */
    public IfNodeCompiler(BSHIfStatement node, CompilationUnit unit) {
        super();
        this.node = node;
        this.unit = unit;
    }

    /**
     * {@inheritDoc}
     */
    public void compile(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        Scope scope = state.newScope(ScopeType.IF_BLOCK);
        scope.installStartLabel(host, state.getUnit().options());
        Label endif = scope.getEndLabel();
        this.compileIfNode(this.node, host, state, endif);
        scope.installEndLabel(host, this.unit.options());
        state.popScope();
    }

    /**
     * @param child {@link SimpleNode}
     * @param host {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param endif Label
     * @throws CompilationFailedException e
     */
    private void compileElseBlock(SimpleNode child, MethodVisitor host, CompilationState state, Label endif)
            throws CompilationFailedException {
        if (child instanceof BSHIfStatement) {
            this.compileIfNode((BSHIfStatement) child, host, state, endif);
        } else {
            // simply compile the child block
            this.unit.compileNode(child, host, state);
            CompilerImpl.popStackIfNeeded(host, state);
        }
    }

    /**
     * @param child {@link BSHIfStatement}
     * @param host {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param endif Label
     * @throws CompilationFailedException e
     */
    private void compileIfNode(BSHIfStatement child, MethodVisitor host, CompilationState state, Label endif)
            throws CompilationFailedException {
        Label nextPiece;
        if (child.jjtGetNumChildren() > 2) {
            nextPiece = new Label();
        } else {
            nextPiece = endif;
        }
        // compiling condition
        this.unit.compileNode(child.getChild(0), host, state);
        this.unit.getCompilerManager().coerceStackTopTo(host, JavaClassWrapper.BOOLEAN_WRAPPER, state);
        state.assertStackTopType(JavaClassWrapper.BOOLEAN_WRAPPER);
        state.popStack();
        host.visitJumpInsn(Opcodes.IFEQ, nextPiece);

        // compiling inner expression
        this.unit.compileNode(child.getChild(1), host, state);
        // empty stack if needed
        CompilerImpl.popStackIfNeeded(host, state);
        if (child.jjtGetNumChildren() > 2) {
            host.visitJumpInsn(Opcodes.GOTO, endif);
            host.visitLabel(nextPiece);
            this.compileElseBlock(child.getChild(2), host, state, endif);
        }
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper nodeResultType(Scope scope) throws CompilationFailedException {
        return JavaClassWrapper.VOID_WRAPPER;
    }

}
